#ifndef CODEDBLOCK_HH
#define CODEDBLOCK_HH

#include <click/config.h>
#include <click/string.hh>
#include <click/packet.hh>

#define DEBUG 0
#define DEBUG_RANK 0
#define DEBUG_ACK 0
// print actual coefficients in logs
#define DEBUG_CB 0


#define massert(v,arg...) do{                             \
                            if(!(v)) click_chatter(arg);  \
                            assert(v);                    \
                          }while(0)

CLICK_DECLS

typedef uint8_t uchar;

/**
 * Finite Field operations for GF
 */
#include "galoisfield.hh"
extern GaloisField<8> gf256;
static inline uchar __field_mul(uchar a, uchar b) { return gf256.mul(a,b); }
static inline uchar __field_inv(uchar a) { return gf256.inverse(a); }

/**
 * Header for CodedBlock
 */
CLICK_SIZE_PACKED_STRUCTURE(struct more_block {,
    uint16_t _k;
    uint16_t _datasize; // size of the data block
});

class Matrix;
class PartialGEMatrix;

/**
 * CodedBlock comes in different flavors.
 * dataless = stores just the coefficients (unwrapped).
 * datafull = stores the data and wrapped coefficients in an underlying Packet
 *   to wrap the unwrapped coefficients into the Packet call take()
 *   BUT ONLY ONCE!
 */
class CodedBlock  {
protected:
    friend class FullGEMatrix;
    int _k; // K = number of coefficients = number of bytes needed to store the coefficients

    uchar *_coeff; // the coefficients
    int _first; // first non-0 coeff.
    int _degree; // total non-0 coeff.

    // should be called after each coefficient update
    void update() {
        int i;
        for(i = 0; i < _k && !_coeff[i]; ++i);
        _first = i;
        _degree = 0;
        for(i = _first; i < _k; ++i) {
            if(_coeff[i])
                ++_degree;
        }
    }

    /* datafull version */
    int _datasize; // NOTE: actual data size
    uchar *_data;
    // only the pointer is mutable here
    mutable WritablePacket * _p; // where _data is stored

    uchar &operator[](int i) { return _coeff[i]; }

    void setup(int k, int datasize, WritablePacket *p = 0);
public:
    // unwrapping
    CodedBlock(Packet *p, bool withData);
    // encoding - makes a blank packet
    CodedBlock(int k, int datasize, WritablePacket *p = 0) {
        // if p is given it should point at more_block!
        if(p) {
            // make it point at the datachunk
            p->pull(sizeof(more_block) + k);
        }
        setup(k, datasize, p);
        if(_data)
            memset(_data, 0, _datasize);
        memset(_coeff, 0, _k);
        _first = _k;
        _degree = 0;
    }
    // sourcing - takes packet
    CodedBlock(int k, int slot, int datasize, WritablePacket * p);
    // copying coefficients => datasize=0
    CodedBlock(const CodedBlock &cb);

    ~CodedBlock();

    // sourcing (into an already allocated CodedBlock)
    // leave p = 0 to fake it
    void source(int slot, Packet *p = 0);
    // the opposite of source
    Packet *sink() const; // makes the codedblock useless

    /* store coefficients into the packet
       then take the packet away, the block is useless afterwards */
    WritablePacket *take() {
        assert(_p);
        assert(!_p->shared());
        memcpy(_data - _k, _coeff, _k);
        WritablePacket *p = _p; _p = 0;
        return p; // starts with the more_block
    }
    // take a peek into the prepared packet
    WritablePacket *prepare() {
        assert(_p);
        assert(!_p->shared());
        return _p;
    }

    void add(const CodedBlock &cb, uchar coeff);
    void div(uchar coeff);
    void add_all(const PartialGEMatrix &m);
    void normalize() { div(_coeff[_first]); }

    int operator[](int i) const { return _coeff[i]; }

    String print() const;
    int degree() const { return _degree; }
    int first() const { return _first; }
//    const uchar *data() const { return _data; }
//    const uchar *coeff() const { return _coeff; }
    int datasize() const { return _datasize; }
    int k() const { return _k; }

    static inline int random_range(int min, int max) {
        return min + ((random() ^ (random() >> 8)) % (max-min+1));
    }
};

CLICK_ENDDECLS

#endif //CODEDBLOCK_HH
